﻿/* LICENCE
 * 
 * You can use this file in binary form in your program for free, as long as you credit its author, 
 * the great (and humble) Louis Lecailliez.
 * You can share the source code along with your modifications and this licence.
 *
 * 
 * Ce code vous permet de convertir des chaînes de caractères latins en leur équivalent phonétique dans les syllabaires
 * japonais katakana et hiragana.
 * Les régles de conversions sont celles de l'IME japonais sous Windows.
 * 
 * Exemple du mode  hiragana : arigatou -> ありがとう
 *                  katakana : furansu -> フランス
 *                  romaji   : lol -> lol
 */


using System;
using System.Text;

namespace Louis.Lecailliez.Japanese.IME
{
    /// <summary>
    /// The running modes of an IME.
    /// The mode show which character set is used to perform substitution in input romaji strings.
    /// </summary>
    public enum IMEMode
    {
        Hiragana = 0,
        Katakana = 1,
        Romaji = 2
    }

    public interface IKanaIME
    {
        /// <summary>
        /// Get or set which is the character set used by the IME to 
        /// perform substitution.
        /// </summary>
        IMEMode Mode { get; set; }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="text">String to parse against the IME.</param>
        /// <returns>Input with all possible substitution made.</returns>
        string Substitute(string text);
    }

    public class KanaIME : IKanaIME
    {
        /// <summary>
        /// Store the current Substitution table, that is a reference to either
        /// SubstitutionTableHiragana or SubstitutionTableKatakana.
        /// </summary>
        protected string[][] SubstitutionTableInUse;

        /// <summary>
        /// Public contructor.
        /// Default substitution mode is Hiragana.
        /// </summary>
        public KanaIME()
        {
            SubstitutionTableInUse = SubstitutionTableHiragana;
        }

        /// <summary>
        /// The kind of substitution currently in use.
        /// </summary>
        IMEMode mode = IMEMode.Hiragana;

        /// <summary>
        /// Get or Set the current Mode.
        /// </summary>
        public IMEMode Mode
        {
            get
            {
                return mode;
            }
            set
            {
                switch (value)
                {
                    case IMEMode.Hiragana:
                        SubstitutionTableInUse = SubstitutionTableHiragana;
                        break;
                    case IMEMode.Katakana:
                        SubstitutionTableInUse = SubstitutionTableKatakana;
                        break;
                    case IMEMode.Romaji:
                        break;
                    default:
                        break;
                }
                mode = value;
            }
        }

        public string Substitute(string text)
        {
            string res = "";
            for (int i = 0; i < text.Length; i++)
            {
                res = InternalSubstitue(res + text[i]);
            }
            return res;
        }

        /// <summary>
        /// This method acts on a whole string, and replace as much as it can using a 
        /// longest match algorithm.
        /// 
        /// The result SHOULD NOT be used directly, because it's not how an IME should parse a string.
        /// The fact is, each time a pattern is found for a sequence, the algorithm update a pointer
        /// to the end of the found pattern which lead in unsubstitued characters.
        /// 
        /// For example 'nb' match 'んb', so in a string like 'nba', the algorithm will remplace
        /// 'nb' by 'んb' and then find match for 'a', so the 'b' will never be used in another combinaison,
        /// and the resultat string is 'んbあ'.
        /// This is not the correct behavior of an IME : 'んば' is the correct result.
        /// </summary>
        /// <param name="text">Text to works on.</param>
        /// <returns></returns>
        public string InternalSubstitue(string sourceText)
        {
            sourceText = sourceText.ToLowerInvariant();
            if (this.Mode == IMEMode.Romaji)
                return sourceText;
            int position_pointer = 0;
            int segment_size = 4;
            string temp_segment = "";
            StringBuilder resultText = new StringBuilder();

            // boucle dont la condition d'arrêt doit prendre en compte la taille du segment (doit être supérieure à 0)
            // ainsi que la taille du texte : il ne faut pas que position_pointer + segment_size > sourceText.Length

            while (position_pointer < sourceText.Length)
            {
            // faire un filtre avant de s'attaquer au segment en cours pour vérifier si le 
            // prochain character est bien un kanji
            exit:
                segment_size = 4;

                for (int i = segment_size; i > 0; i--, segment_size--)
                {
                    while (position_pointer + segment_size > sourceText.Length)
                    {
                        segment_size--;
                        if (segment_size == 0)
                            return resultText.ToString();
                    }
                    temp_segment = sourceText.Substring(position_pointer, segment_size);

                    string tmp = SearchAssociateKana(temp_segment, SubstitutionTableInUse[segment_size - 1]);

                    if (!tmp.Equals(temp_segment))
                    {
                        resultText.Append(tmp);

                        position_pointer += segment_size;
                        goto exit;
                    }
                    if (segment_size == 1)
                    {
                        resultText.Append(temp_segment);
                        position_pointer++;
                        goto exit;
                    }
                }
            }
            return resultText.ToString();
        }

        /// <summary>
        /// Return the kana string corresponding to a sequence of letters.
        /// If there is no corresponding kana string, letterSequence is returned.
        /// </summary>
        /// <param name="letterSequence">Sequence to parse.</param>
        /// <param name="table">Associative letter-kana table (basically an A-LIST in LISP)</param>
        /// <returns>Reprensation of letterSequence in kana if it exists, letterSequence ortherwise.</returns>
        protected string SearchAssociateKana(string letterSequence, string[] table)
        {
            int index = Array.IndexOf(table, letterSequence);
            string returnedText = letterSequence;
            if (index != -1 && (index % 2 == 0)) // hack to avoid 'reverse search' (?)
            {
                returnedText = table[index + 1];
            }
            return returnedText;
        }

        #region TableHiragana
        private string[][] SubstitutionTableHiragana = new string[][] {
            new string[] {
                "a", "あ", "i", "い", "u", "う", "e", "え", "o", "お", ".", "。"

            },
            new string[] {
                "ka","か","ki","き","ku","く","ke","け","ko","こ",
                "sa","さ","si","し","su","す","se","せ","so","そ",
                "ta","た","ti","ち","tu","つ","te","て","to","と",
                "na","な","ni","に","nu","ぬ","ne","ね","no","の",
                "ha","は","hi","ひ","hu","ふ","he","へ","ho","ほ",
                "ma","ま","mi","み","mu","む","me","め","mo","も",
                "ya","や","yi","い","yu","ゆ","ye","いぇ","yo","よ",
                "ra","ら","ri","り","ru","る","re","れ","ro","ろ",
                "wa","わ","wi","ゐ","wu","う","we","ゑ","wo","を",
                "ga","が","gi","ぎ","gu","ぐ","ge","げ","go","ご",
                "za","ざ","zi","じ","zu","ず","ze","ぜ","zo","ぞ",
                "da","だ","di","ぢ","du","づ","de","で","do","ど",
                "ba","ば","bi","び","bu","ぶ","be","べ","bo","ぼ",
                "pa","ぱ","pi","ぴ","pu","ぷ","pe","ぺ","po","ぽ",
                "ja","じゃ","ji","じ","ju","じゅ","je","じぇ","jo","じょ",
                "nn", "ん", "n.","ん。",
                "fa","ふぁ","fi","ふぃ","fu","ふ","fe","ふぇ","fo","ふぉ",
                "qa","くぁ","qi","くぃ","qu","く","qe","くぇ","qo","くぉ",
                "la","ぁ","li","ぃ","lu","ぅ","le","ぇ","lo","ぉ",
                "xa","ぁ","xi","ぃ","xu","ぅ","xe","ぇ","xo","ぉ",
                "ca","か","ci","し","cu","く","ce","せ","co","こ",
                // n + other exceptions
                "nq","んq","nw","んw","nr","んr","nt","んt","np","んp","nd","んd",
                "nf","んf","ng","んg","nh","んh","nj","んj","nk","んk","nl","んl",
                "nz","んz","nx","んx","nc","んc","nv","んv","nb","んb","nm","んm",
                "ns","んs"
            },
            new string[] {
                "kya","きゃ","kyi","きぃ","kyu","きゅ","kye","きぇ","kyo","きょ",
                "sha","しゃ","shi","し","shu","しゅ","she","しぇ","sho","しょ",
                "sya","しゃ","syi","しぃ","syu","しゅ","sye","しぇ","syo","しょ",
                "cha","ちゃ","chi","ち","chu","ちゅ","che","ちぇ","cho","ちょ",
                "tya","ちゃ","tyi","ちぃ","tyu","ちゅ","tye","ちぇ","tyo","ちょ",         
                "nya","にゃ","nyi","にぃ","nyu","にゅ","nye","にぇ","nyo","にょ",
                "hya","ひゃ","hyi","ひぃ","hyu","ひゅ","hye","ひぇ","hyo","ひょ",
                "mya","みゃ","myi","みぃ","myu","みゅ","mye","みぇ","myo","みょ",
                "rya","りゃ","ryi","りぃ","ryu","りゅ","rye","りぇ","ryo","りょ",     
                "gya","ぎゃ","gyi","ぎぃ","gyu","ぎゅ","gye","ぎぇ","gyo","ぎょ",
                "zya","じゃ","zyi","じぃ","zyu","じゅ","zye","じぇ","zyo","じょ",
                "bya","びゃ","byi","びぃ","byu","びゅ","bye","びぇ","byo","びょ",
                "pya","ぴゃ","pyi","ぴぃ","pyu","ぴゅ","pye","ぴぇ","pyo","ぴょ",
                "kka","っか","kki","っき","kku","っく","kke","っけ","kko","っこ",
                "ssa","っさ","ssi","っし","ssu","っす","sse","っせ","sso","っそ",
                "tta","った","tti","っち","ttu","っつ","tte","って","tto","っと",
                "hha","っは","hhi","っひ","hhu","っふ","hhe","っへ","hho","っほ",
                "mma","っま","mmi","っみ","mmu","っむ","mme","っめ","mmo","っも",
                "yya","っや","yyi","っい","yyu","っゆ","yye","っいぇ","yyo","っよ",
                "rra","っら","rri","っり","rru","っる","rre","っれ","rro","っろ",
                "ffa","っふぁ","ffi","っふぃ","っふ","ffu","ffe","っふぇ","ffo","っふぉ",
                "wwa","っわ","wwi","っうぃ","wwu","っう","wwe","っうぇ","wwo","っを",
                "gga","っが","ggi","っぎ","ggu","っぐ","gge","っげ","ggo","っご",
                "zza","っざ","zzi","っじ","zzu","っず","zze","っぜ","zzo","っぞ",
                "dda","っだ","ddi","っぢ","ddu","っづ","dde","っで","ddo","っど",
                "bba","っば","bbi","っび","bbu","っぶ","bbe","っべ","bbo","っぼ",
                "ppa","っぱ","ppi","っぴ","ppu","っぷ","ppe","っぺ","ppo","っぽ",
                "jja","っじゃ","jji","っじ","jju","っじゅ","jje","っじぇ","jjo","っじょ",
                "lya","ゃ","lyi","ぃ","lyu","ゅ","lye","ぇ","lyo","ょ",
            "tsu","つ", "ltu", "っ"
            },
            new string[] {
                "ltsu", "っ",
                "kkya","っきゃ","kkyi","っきぃ","kkyu","っきゅ","kkye","っきぇ","kkyo","っきょ",
                "ssha","っしゃ","sshi","っし","sshu","っしゅ","sshe","っしぇ","ssho","っしょ",
                "ssya","っしゃ","ssyi","っしぃ","ssyu","っしゅ","ssye","っしぇ","ssyo","っしょ",
                "ccha","っちゃ","cchi","っち","cchu","っちゅ","cche","っちぇ","ccho","っちょ",
                "ttya","っちゃ","ttyi","っちぃ","ttyu","っちゅ","ttye","っちぇ","ttyo","っちょ",
                "hhya","っひゃ","hhyi","っひぃ","hhyu","っひゅ","hhye","っひぇ","hhyo","っひょ",
                "mmya","っみゃ","mmyi","っみぃ","mmyu","っみゅ","mmye","っみぇ","mmyo","っみょ",
                "rrya","っりゃ","rryi","っりぃ","rryu","っりゅ","rrye","っりぇ","rryo","っりょ",
                "ggya","っぎゃ","ggyi","っぎぃ","ggyu","っぎゅ","ggye","っぎぇ","ggyo","っぎょ",
                "zzya","っじゃ","zzyi","っじぃ","zzyu","っじゅ","zzye","っじぇ","zzyo","っじょ",
                "bbya","っびゃ","bbyi","っびぃ","bbyu","っびゅ","bbye","っびぇ","bbyo","っびょ",
                "ppya","っぴゃ","ppyi","っぴぃ","ppyu","っぴゅ","ppye","っぴぇ","ppyo","っぴょ"
            }

         };
        #endregion

        string[] NExceptions = new string[] { 
            "nb","nc","nd","nf","ng","nh","nj","nk","nl",
            "nm","np","nq","nr","ns","nt","nv","nw","nx","nz"
        };

        #region TableKatakana
        private string[][] SubstitutionTableKatakana = new string[][] {
            new string[] {
                "a", "ア", "i", "イ", "u", "ウ", "e", "エ", "o", "オ", ".", "。", "-", "ー", "_", "ー"
            },
            new string[] {
                "ka","カ","ki","キ","ku","ク","ke","ケ","ko","コ",
                "sa","サ","si","シ","su","ス","se","セ","so","ソ",
                "ta","タ","ti","チ","tu","ツ","te","テ","to","ト",
                "na","ナ","ni","ニ","nu","ヌ","ne","ネ","no","ノ",
                "ha","ハ","hi","ヒ","hu","フ","he","ヘ","ho","ホ",
                "ma","マ","mi","ミ","mu","ム","me","メ","mo","モ",
                "ya","ヤ","yi","イ","yu","ユ","ye","イェ","yo","ヨ",
                "ra","ラ","ri","リ","ru","ル","re","レ","ro","ロ",
                "wa","ワ","wi","ジ","wu","ウ","we","ウェ","wo","ヲ",
                "ga","ガ","gi","ギ","gu","グ","ge","ゲ","go","ゴ",
                "za","ザ","zi","ジ","zu","ズ","ze","ゼ","zo","ゾ",
                "da","ダ","di","ヂ","du","ヅ","de","デ","do","ド",
                "ba","バ","bi","ビ","bu","ブ","be","ベ","bo","ボ",
                "pa","パ","pi","ピ","pu","プ","pe","ペ","po","ポ",
                "ja","ジャ","ji","ジ","ju","ジュ","je","ジェ","jo","ジョ",
                "nn", "ン", "n.","ン。",
                "fa","ファ","fi","フィ","fu","フ","fe","フェ","fo","フォ",
                "qa","クァ","qi","クィ","qu","ク","qe","クェ","qo","クォ",
                "la","ァ","li","ィ","lu","ゥ","le","ェ","lo","ォ",
                "xa","ァ","xi","ィ","xu","ゥ","xe","ェ","xo","ォ",
                "ca","カ","ci","シ","cu","ク","ce","セ","co","コ",
                // n + other exceptions
                "nq","ンq","nw","ンw","nr","ンr","nt","ンt","np","ンp","nd","ンd",
                "nf","ンf","ng","ンg","nh","ンh","nj","ンj","nk","ンk","nl","ンl",
                "nz","ンz","nx","ンx","nc","ンc","nv","ンv","nb","ンb","nm","ンm",
                "ns","ンs"
            },
            new string[] {
                "kya","キャ","kyi","キィ","kyu","キュ","kye","キェ","kyo","キョ",
                "sha","シャ","shi","シ","shu","シュ","she","シェ","sho","ショ",
                "sya","シャ","syi","シィ","syu","シュ","sye","シェ","syo","ショ",
                "cha","チャ","chi","チ","chu","チュ","che","チェ","cho","チョ",
                "tya","チャ","tyi","チィ","tyu","チュ","tye","チェ","tyo","チョ",         
                "nya","ニャ","nyi","ニィ","nyu","ニュ","nye","ニェ","nyo","ニョ",
                "hya","ヒャ","hyi","ヒィ","hyu","ヒュ","hye","ヒェ","hyo","ヒョ",
                "mya","ミャ","myi","ミィ","myu","ミュ","mye","ミェ","myo","ミョ",
                "rya","リャ","ryi","リィ","ryu","リュ","rye","リェ","ryo","リョ",     
                "gya","ギャ","gyi","ギィ","gyu","ギュ","gye","ギェ","gyo","ギョ",
                "zya","ジャ","zyi","ジィ","zyu","ジュ","zye","ジェ","zyo","ジョ",
                "bya","ビャ","byi","ビィ","byu","ビュ","bye","ビェ","byo","ビョ",
                "pya","ピャ","pyi","ピィ","pyu","ピュ","pye","ピェ","pyo","ピョ",
                "kka","ッカ","kki","ッキ","kku","ック","kke","ッケ","kko","ッコ",
                "ssa","ッサ","ssi","ッシ","ssu","ッス","sse","ッセ","sso","ッソ",
                "tta","ッタ","tti","ッチ","ttu","ッツ","tte","ッテ","tto","ット",
                "hha","ッハ","hhi","ッヒ","hhu","ッフ","hhe","ッヘ","hho","ッホ",
                "mma","ッマ","mmi","ッミ","mmu","ッム","mme","ッメ","mmo","ッモ",
                "yya","ッヤ","yyi","ッイ","yyu","ッユ","yye","ッイェ","yyo","ッヨ",
                "rra","ッラ","rri","ッリ","rru","ッル","rre","ッレ","rro","ッロ",
                "ffa","ッファ","ffi","ッフィ","ッフ","ffu","ffe","ッフェ","ffo","ッフォ",
                "wwa","ッワ","wwi","ッウィ","wwu","ッウ","wwe","ッウェ","wwo","ッヲ",
                "gga","ッガ","ggi","ッギ","ggu","ッグ","gge","ッゲ","ggo","ッゴ",
                "zza","ッザ","zzi","ッジ","zzu","ッズ","zze","ッゼ","zzo","ッゾ",
                "dda","ッダ","ddi","ッヂ","ddu","ッヅ","dde","ッデ","ddo","ッド",
                "bba","ッバ","bbi","ッビ","bbu","ッブ","bbe","ッベ","bbo","ッボ",
                "ppa","ッパ","ppi","ッピ","ppu","ップ","ppe","ッペ","ppo","ッポ",
                "jja","ッジャ","jji","ッジ","jju","ッジュ","jje","ッジェ","jjo","ッジョ",
                "lya","ャ","lyi","ィ","lyu","ュ","lye","ェ","lyo","ョ",
            "tsu","ツ", "ltu", "ッ"
                
            },
            new string[] {
                "ltsu", "ッ",
                "kkya","ッキャ","kkyi","ッキィ","kkyu","ッキュ","kkye","ッキェ","kkyo","ッキョ",
                "ssha","ッシャ","sshi","ッシ","sshu","ッシュ","sshe","ッシェ","ssho","ッショ",
                "ssya","ッシャ","ssyi","ッシィ","ssyu","ッシュ","ssye","ッシェ","ssyo","ッショ",
                "ccha","ッチャ","cchi","ッチ","cchu","ッチュ","cche","ッチェ","ccho","ッチョ",
                "ttya","ッチャ","ttyi","ッチィ","ttyu","ッチュ","ttye","ッチェ","ttyo","ッチョ",
                "hhya","ッヒャ","hhyi","ッヒィ","hhyu","ッヒュ","hhye","ッヒェ","hhyo","ッヒョ",
                "mmya","ッミャ","mmyi","ッミィ","mmyu","ッミュ","mmye","ッミェ","mmyo","ッミョ",
                "rrya","ッリャ","rryi","ッリィ","rryu","ッリュ","rrye","ッリェ","rryo","ッリョ",
                "ggya","ッギャ","ggyi","ッギィ","ggyu","ッギュ","ggye","ッギェ","ggyo","ッギョ",
                "zzya","ッジャ","zzyi","ッジィ","zzyu","ッジュ","zzye","ッジェ","zzyo","ッジョ",
                "bbya","ッビャ","bbyi","ッビィ","bbyu","ッビュ","bbye","ッビェ","bbyo","ッビョ",
                "ppya","ッピャ","ppyi","ッピィ","ppyu","ッピュ","ppye","ッピェ","ppyo","ッピョ"    
            }

         };
        #endregion
    }
}